# Custom Authentication Class
# Implementation similar to gluon.tools

##db=SQLDB("mysql://jschwarzbeck:dderidex@localhost:3306/zforum")

from gluon.html import URL
from gluon.http import redirect
import hashlib

class CustomAuthentication(object):
    """ Role-Based authentication module """

    def __init__(self, request, response, session, cache, T, db):
        self.request = request
        self.response = response
        self.session = session
        self.cache = cache
        self.T = T
        self.db = db
        self._anonymous_user = 'Anonymous User'

    def __call__(self):
        """ Returns the username """
        _auth_name = self.session.auth_alias
        if _auth_name is None:
            _auth_name = self._anonymous_user
        return _auth_name

    def authenticate(self, auth_alias, auth_passwd):
        """ sets authentication for the user """
        auth = False
        self.logout() # Clear up previous session if any
        hash_pwd = hashlib.sha1(auth_passwd).hexdigest()
        rows = self.db((self.db.auth_users.auth_alias==auth_alias) & (self.db.auth_users.auth_passwd==hash_pwd) & (self.db.auth_users.is_enabled==True)).select()
        if rows:
            self.session.auth_alias = auth_alias
            auth = True
        return auth
    
    def authenticate_google(self, auth_alias, auth_passwd):
        """ Provides a simple method for authentication in google using web2py's internal methods """
        # If the user logs in and there is already an account for him/her, update the password only,
        # If ther user does not exist, then insert it with the appropriate credentials.
        # IMPORTANT: For google accounts, the "auth_alias" parameter will come in as "user@gmail.com", or
        # "user@googlehosteddomain.com", it is important to do the following:
        # pyForum's auth_email will be auth_alias
        # pyForum's auth_alias will be auth_alias without the "@" and the domain part
        auth_alias_no_domain = auth_alias[:auth_alias.find('@')]
        hash_passwd = hashlib.sha1(auth_passwd).hexdigest()
        if self.db(self.db.auth_users.auth_alias.upper()==auth_alias_no_domain.upper()).select(self.db.auth_users.id):
            # User already exists, update passwd only
            self.db(self.db.auth_users.auth_alias==auth_alias_no_domain).update(auth_passwd=hash_passwd)
        else:
            # New User
            auth_user_id = self.db.auth_users.insert(auth_alias=auth_alias_no_domain, auth_email=auth_alias, auth_passwd=hash_passwd, auth_created_on=self.request.now, auth_modified_on=self.request.now)
            # Add the default role of zMember (NOTE: THIS ROLE MUST EXIST)
            auth_role_id = self.db(self.db.auth_roles.auth_role_name=='zMember').select(self.db.auth_roles.id)[0].id
            self.db.auth_user_role.insert(auth_user_id=auth_user_id, auth_role_id=auth_role_id)
        self.session.auth_alias = auth_alias_no_domain
        return True

    def logout(self):
        """ Clear the session """
        self.session.auth_alias = None

    def has_role(self, roles):
        """ Receives a comma-separated string containing the roles to check and will return True if the user contains any of the passed roles  """
        hasrole = False
        roles_to_check = roles.split(',')
        roles_found = []
        if self.is_auth():
            auth_alias = self.session.auth_alias
            # select
            #   ar.auth_role_name
            # from
            #   auth_roles as ar,
            #   auth_user_role as aur,
            #   auth_users as au
            # where
            #   au.auth_alias = %(auth_alias)s
            #   and au.id = aur.auth_user_id
            #   and aur.auth_role_id = ar.id
            user_roles = self.db((self.db.auth_users.auth_alias == auth_alias) &\
                                 (self.db.auth_users.id == self.db.auth_user_role.auth_user_id) &\
                                 (self.db.auth_user_role.auth_role_id == self.db.auth_roles.id)).select(self.db.auth_roles.auth_role_name)
            if user_roles:
                roles_found = [each_role for each_role in user_roles if each_role.auth_role_name in roles_to_check]
                if roles_found:
                    hasrole = True
        return hasrole

    def get_roles(self):
        """ Returns a list of roles the user belongs to """
        roles = []
        if self.is_auth():
            auth_alias = self.get_user_name()
            user_roles = self.db((self.db.auth_users.auth_alias==auth_alias) &\
                                 (self.db.auth_users.id==self.db.auth_user_role.auth_user_id) &\
                                 (self.db.auth_user_role.auth_role_id==self.db.auth_roles.id)).select(self.db.auth_roles.auth_role_name)
            if user_roles:
                roles = [each_role.auth_role_name for each_role in user_roles]
        return roles    

    def get_user_name(self):
        """ same as __call__ - returns the username (alias) """
        _auth_name = self.session.auth_alias
        if _auth_name is None:
            _auth_name = self._anonymous_user
        return _auth_name

    def get_user_email(self):
        """ If auth, gets the user alias from the database """
        if self.is_auth():
            user_email = self.db(self.db.auth_users.auth_alias==self.get_user_name()).select(self.db.auth_users.auth_email)[0].auth_email
        else:
            user_email = None
        return user_email

    def is_auth(self):
        """ True if the user has been authenticated in the system, false otherwise """
        return self.session.auth_alias is not None

    def is_admin(self):
        """ This is a hack-y method (or shortcut) that can become useful in the future if the
        developer decides that "zAdministrator" should not be the only "admin" in the system """
        return self.has_role('zAdministrator') # Add "more" as needed

    def requires_login(self):
        """ Decorator Helper to aid in determine whether a controller needs specific access """
        def wrapper(func):

            def f(*args, **kwargs):
                if not self.is_auth():
                    return redirect(URL(r=self.request, c='default', f='login'))
                return func(*args, **kwargs)

            return f

        return wrapper

    def requires_role(self, roles):
        """ Decorator Helper to aid in determine whether a controller needs specific access """
        def wrapper(func):

            def f(*args, **kwargs):
                if not self.has_role(roles):
                    return redirect(URL(r=self.request, c='default', f='login'))
                return func(*args, **kwargs)

            return f

        return wrapper

# Instantiate our authentication object, visible throughout all applications (If this is put in the Models section, that is).
# auth_user = CustomAuthentication(request, response, session, cache, T, db)
        
